package com.xquant.pricing.calc.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.xquant.common.core.utils.ParseUtils;
import com.xquant.common.engine.xPP.request.UnderlyGreeksParam;
import com.xquant.common.engine.xPP.response.CalcScene;
import com.xquant.pricing.utils.ContextHolder;
import com.xquant.pricing.utils.DateUtils;
import com.xquant.pricing.utils.IRBaseException;
import com.xquant.pricing.utils.SysUtils;
import com.xquant.common.engine.xPP.cxf.XQuantPPService;
import com.xquant.common.engine.xPP.cxf.XQuantPPStub;
import com.xquant.common.engine.xPP.cxf.webservice.CalcEngine;
import com.xquant.common.engine.xPP.cxf.webservice.CalcEngineResponse;
import com.xquant.common.engine.xPP.request.CalcEngineParam;
import com.xquant.common.engine.xPP.response.CalcResult;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static java.math.BigDecimal.ROUND_HALF_UP;

import javax.xml.ws.WebServiceException;
import javax.xml.ws.soap.SOAPFaultException;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.*;

@Component
public class CalcEngineWebservice {

    private static final Logger logger = LoggerFactory.getLogger(CalcEngineWebservice.class);
    @Autowired
    private XQuantPPService ppService;

    Queue<CalcEngineParam> calcQueuePQ = new PriorityQueue<CalcEngineParam>(new CustomJsonComparator());

    /**
     * 计算
     *
     * @param
     * @param
     * @param param
     * @return
     */
    public CalcResult doCalcEngine(Map<String, Object> downparam, CalcEngineParam param) {
        //calcQueuePQ.add(calcEngineParam);
        //   CalcEngineParam param = new CalcEngineParam();
        // param = calcQueuePQ.poll();
        /*设置希腊字母偏移*/
        param.getCalcPricingParam().setGreeksParams(
                Arrays.asList(
                        new UnderlyGreeksParam()
                                .setUnderlyingID(ObjectUtil.isEmpty(param.getCalcPricingParam().getUnderlyingPricingParams())?"":param.getCalcPricingParam().getUnderlyingPricingParams().get(0).getUnderlyingID())
                                .setRateIncMode("ABS")
                                .setRateInc("0.01") //Rho
                                .setSpotIncMode("REL")
                                .setSpotInc("0.01")
                                .setVolIncMode("ABS")
                                .setVolInc("0.01")
                )
        );

        param.convertToBASE64();

        CalcResult ret = new CalcResult();

        //特殊处理单鲨期权，设置对应的值,因为之前summit导入交易的时候只设置了对应的比例，没有设置对应的值，计算结果会有出入
        if (param.getInstrument().getProductType().equals("SingleSharkFin")) {
            ArrayList customLegs = (ArrayList) param.getInstrument().getTermsheet().get("customLegs");
            if ( customLegs.get(0) instanceof LinkedHashMap ) {
                LinkedHashMap customLeg = (LinkedHashMap) customLegs.get(0);
                ArrayList underlyers = (ArrayList) customLeg.get("underlyers");
                LinkedHashMap underlyer = (LinkedHashMap) underlyers.get(0);
                BigDecimal spotPrice = ParseUtils.converToBigDecimal4(underlyer.get("spotPrice"));
                if (spotPrice.compareTo(BigDecimal.ZERO) != 0) {
                    LinkedHashMap knock = (LinkedHashMap) customLeg.get("knock");
                    LinkedHashMap trigger = (LinkedHashMap) knock.get("trigger");
                    Object level = trigger.get("level");
                    if (level == null) {
                        BigDecimal levelPercentage = ParseUtils.converToBigDecimal4(trigger.get("levelPercentage"));
                        trigger.put("level", spotPrice.multiply(levelPercentage).setScale(2, ROUND_HALF_UP));
                    }
                    LinkedHashMap unTrigger = (LinkedHashMap) customLeg.get("unTrigger");
                    LinkedHashMap vanillaEuropean = (LinkedHashMap) unTrigger.get("vanillaEuropean");
                    Object strike = vanillaEuropean.get("strike");
                    if (strike.toString().equals("")) {
                        BigDecimal strikePercentage = ParseUtils.converToBigDecimal4(vanillaEuropean.get("strikePercentage"));
                        vanillaEuropean.put("strike", spotPrice.multiply(strikePercentage).setScale(2, ROUND_HALF_UP));
                    }
                }
            }
        }

        String downReq = JSON.toJSONString(param);

        downparam.put("downReq", downReq);

        XQuantPPStub stub = null;
        String priority = param.getPriority();
        int retryCount = 0;
        boolean isRetry = true;
        while (isRetry) {
            isRetry = false;
            if (retryCount > 0) {
                priority = "h";
                logger.info("SOAPFaultException导致的重连");
            }
            try {

                CalcEngine pIn = new CalcEngine();
                pIn.setParam(downReq);
                long a = System.nanoTime();
               /* if ("h".equals(priority)) {
                    stub = ppService.borrowStubPriority();
                } else {
                    stub = ppService.borrowStub();
                }*/
                stub = ppService.borrowStubServer();
                long b = System.nanoTime();
                ContextHolder.getParam().put("getStubTime", b - a);
                downparam.put("downReqTime", DateUtils.getCurrentDateTimeWOnlyMilliS());
                ContextHolder.getParam().put("server", stub.getAddress());
                long start = System.nanoTime();
                CalcEngineResponse res = stub.calcEngine(pIn);
                logger.debug(StrUtil.format("server:{}", stub.getAddress()));
                long end = System.nanoTime();
                ContextHolder.getParam().put("calcTime", end - start);
                // 计算服务返回结果  coupon  expectedRemainTerm
                logger.info(MessageFormat.format("计算引擎耗时:{0}ns========starTime:{1}====endTime:{2}====server:{3}====downReq===={4}downResp====downResp:{5}",
                        end - start,start,end,stub.getAddress(),downReq, res.getResult()));
                downparam.put("downResp", res.getResult());
                downparam.put("downRespTime", DateUtils.getCurrentDateTimeWOnlyMilliS());

                CalcResult calcResult = JSON.parseObject(res.getResult(),CalcResult.class);
                /*
                        P013XEQ-1942 希腊字母Vega和Rho的量纲修改
                        1.Vega值需要除以100，需前端处理下，定价引擎目前不支持；
                        2.Rho的计算，计算配置参数修改一下偏移量为绝对偏移0.01（目前应该使用的是默认配置：绝对偏移0.0001），最终值也要除以100。
                        3.除了试定价模块需要修改，其他有关估值的模块，都需要前端修改（如跑批）。
                 */
                if (ObjectUtil.isNotNull(calcResult)&&!"-1".equals(calcResult.getErrorCode())){
                    /*市场计算时*/
                    if (ObjectUtil.isNotNull(calcResult.getCalcMarket())){
                        String rho = calcResult.getCalcMarket().getContract().getRho();
                        BigDecimal rhoMuled = new BigDecimal(rho).divide(new BigDecimal("100"));
                        calcResult.getCalcMarket().getContract().setRho(rhoMuled.toString());

                        String vega = calcResult.getCalcMarket().getContract().getVega();
                        BigDecimal vegaMuled = new BigDecimal(vega).divide(new BigDecimal("100"));
                        calcResult.getCalcMarket().getContract().setVega(vegaMuled.toString());
                    }
                    if (ObjectUtil.isNotNull(calcResult.getCalcScene())){
                        for (CalcScene calcScene : calcResult.getCalcScene()) {
                            String rho = calcScene.getContract().getRho();
                            BigDecimal rhoMuled = new BigDecimal(rho).divide(new BigDecimal("100"));
                            calcScene.getContract().setRho(rhoMuled.toString());

                            String vega = calcScene.getContract().getVega();
                            BigDecimal vegaMuled = new BigDecimal(vega).divide(new BigDecimal("100"));
                            calcScene.getContract().setVega(vegaMuled.toString());
                        }
                    }
                }
                res.setResult(JSON.toJSONString(calcResult));
                if (StringUtils.isNotEmpty(res.getResult())) {
                    ret = JSON.parseObject(res.getResult(), CalcResult.class);
                }
                ret.setErrorCode(calcResult.getErrorCode());
                ret.setErrorMsg(calcResult.getErrorMsg());
            } catch (SOAPFaultException soapFault) {
                soapFault.printStackTrace();
                ret.setErrorCode("-999");
                Boolean isRestart = true;
                if ("Connection refused: connect".equals(soapFault.getMessage())
                        || "Connection timed out".equals(soapFault.getMessage())
                        || "No route to host: connect".equals(soapFault.getMessage())
                ) {
                    retryCount++;
                    isRetry = true;
                } else if ("Connection reset".equals(soapFault.getMessage())) {
                    ret.setErrorMsg("本次试算导致计算服务重启，请检查条款或者参数是否正确");
                    isRestart = false;
                } else {
                    ret.setErrorMsg(soapFault.getMessage());
                }
                if (stub != null && isRestart) {
                    ppService.setTimeOut(stub);
                }
                logger.error(MessageFormat.format("====error:{0}", SysUtils.getErrorMsg(soapFault)));
            } catch (WebServiceException ex) {
                ret.setErrorCode("-999");
                ret.setErrorMsg("本次试算导致计算服务重启，请检查条款或者参数是否正确");
                logger.error(MessageFormat.format("====error:{0}", SysUtils.getErrorMsg(ex)));
                ex.printStackTrace();
            } catch (IRBaseException ex) {
                ret.setErrorCode("-999");
                ret.setErrorMsg(ex.getMessage());
                ex.printStackTrace();
                logger.error(MessageFormat.format("====error:{0}", SysUtils.getErrorMsg(ex)));
            } catch (Exception ex) {
                ret.setErrorCode("-999");
                ret.setErrorMsg("未知错误");
                ex.printStackTrace();
                logger.error(MessageFormat.format("====error:{0}", SysUtils.getErrorMsg(ex)));
            } finally {
                if (stub != null) {
                    ppService.returnStub(stub);
                }
                if (ContextHolder.getParam().get("downparams") != null && retryCount == 0) {
                    ((List) ContextHolder.getParam().get("downparams")).add(downparam);
                }
            }
        }

        return ret;
    }


    public CalcResult doCalcEquityOptionImplContract(Map<String, Object> downparam, CalcEngineParam param) {
        CalcResult calcResult = new CalcResult();
        return null;
    }

}
